﻿#region copyright
//<copyright>
// Copyright(C) 2012 TrackerRealm Corporation
// This file is part of the open source project - Jazz. http://jazz.codeplex.com
// 
// Jazz is open software: you can redistribute it and/or modify it 
// under the terms of the GNU Affero General Public License (AGPL) as published by 
// the Free Software Foundation, version 3 of the License.
// 
// Jazz is distributed in the hope that it will be useful, 
// but WITHOUT ANY WARRANTY; without even the implied warranty 
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
//  See the GNU Affero General Public License (AGPL) for more details.
// 
// You should have received a copy of the GNU General Public 
// License along with Jazz.  If not, see <http://www.gnu.org/licenses/>.
//
// REMOVAL OF THIS NOTICE IS VIOLATION OF THE COPYRIGHT. 
//</copyright>
#endregion
using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using TrackerRealm.Jazz.Common;

namespace TrackerRealm.Jazz.Client
{
    /// <summary>
    /// Stores '<see cref="IjStorageUnit"/>' objects by name.
    /// Supports duplicate names.
    /// </summary>
    /// <typeparam name="U"></typeparam>
    public class CacheByName<U> where U : IjStorageUnit
    {
        private Dictionary<string, List<U>> dict = new Dictionary<string, List<U>>();
        private ClientNexusBase nexus;
        /// <summary>
        /// 
        /// </summary>
        /// <param name="nexus"></param>
        public CacheByName(ClientNexusBase nexus)
        {
            this.nexus = nexus;
        }
       
        #region Add
        /// <summary>
        /// 
        /// </summary>
        /// <param name="unit"></param>
        internal void Add(U unit)
        {
            jObject wf = unit as jObject;
            if (wf == null)
                Add(unit, false);
            else
                Add(unit, wf.IsNameUnique);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="unit"></param>
        /// <param name="isNameUnique"></param>
        internal void Add(U unit, bool isNameUnique)
        {
            if (unit.Name == "")
                throw new Exception(string.Format(
                    "TjCacheByName.Add method: The unit parameter is not allowed to have a blank 'Name'.  " +
                    "Unit details: Guid='{0}', FullName='{1}'.", unit.ID, unit.FullName));
            List<U> list;

            if (!dict.ContainsKey(unit.Name))
            {
                list = new List<U>();
                dict.Add(unit.Name, list);
            }
            list = dict[unit.Name];
            if (list.Contains(unit)) return;
            // test for duplicate Units
            if(unit is jObject)
            {
                jObject wf = unit as jObject;
                if (isNameUnique)
                {
                    if (list.FirstOrDefault(u =>
                        u.GetType() == unit.GetType()) != null)
                    {
                        this.nexus.Cache.Remove(unit.ID);
                        throw new InvalidOperationException(string.Format("The jazz Object must have a unique name. The name '{0}' is already in use.", unit.Name));
                    }
                    if (!wf.IsEmpty)
                    {
                        if (list.FirstOrDefault(u => u.GetType().IsSubclassOf(unit.GetType())) != null)
                        {
                            this.nexus.Cache.Remove(unit.ID);
                            throw new InvalidOperationException(string.Format("The jazz object must have a unique name. The name '{0}' is already in use by an instance of type '{1}'.",
                                unit.Name,
                                list.FirstOrDefault(u => u.GetType().IsSubclassOf(unit.GetType())).GetType().Name));
                        }
                        if (list.FirstOrDefault(u => unit.GetType().IsSubclassOf(u.GetType())) != null)
                        {
                            this.nexus.Cache.Remove(unit.ID);
                            throw new InvalidOperationException(string.Format("The jazz object must have a unique name. The name '{0}' is already in use by an instance of type '{1}'.",
                                unit.Name,
                                list.FirstOrDefault(u => unit.GetType().IsSubclassOf(u.GetType())).GetType().Name));
                        }
                    }
                }
            }
            list.Add(unit);

        }
        #endregion
        #region Remove
        /// <summary>
        /// 
        /// </summary>
        /// <param name="unit"></param>
        internal void Remove(U unit)
        {
            List<U> list = null;
            if (!this.dict.TryGetValue(unit.Name, out list))
                throw new Exception(string.Format(
                    "TjCacheByName.Remove:  The unit with the name '{0}' is not in the dictionary.",
                    unit.Name));
            if (!list.Contains(unit))
                throw new Exception(string.Format(
                    "TjCacheByName.Remove:  The unit with the name '{0}' is not in the dictionary list.",
                    unit.Name));
            list.Remove(unit);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="unit"></param>
        internal bool TryRemove(U unit)
        {
            List<U> list = null;
            if (!this.dict.TryGetValue(unit.Name, out list))
                return false;
            return list.Remove(unit);
        }
        #endregion
        #region GetEnumerator
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public Dictionary<string, List<U>>.Enumerator GetEnumerator()
        {
            return this.dict.GetEnumerator();
        }
        #endregion
        #region Clear
        /// <summary>
        /// 
        /// </summary>
        internal void Clear()
        {
            dict.Clear();
        }
        #endregion
        #region Contains
        /// <summary>
        /// Returns true if the a unit of type 'T' is with the 'key' is found
        /// in this collection.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <returns></returns>
        public bool Contains<T>(string key) where T : class, IjStorageUnit
        {
            if (!dict.ContainsKey(key)) return false;
            return (dict[key].FirstOrDefault(u => u is T) != null);
        }
        /// <summary>
        /// Returns true if the unit is in the named cache.
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public bool Contains(U obj)
        {
            if (obj.Name == null || obj.Name == "") return false;
            if (!dict.ContainsKey(obj.Name)) return false;
            return (dict[obj.Name].FirstOrDefault(u => u.ID == obj.ID) != null);
        }
        #endregion
        #region Units
        /// <summary>
        /// Returns all units with the 'key' Name.
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public IEnumerable<U> GetUnits(string key)
        {
            if (!dict.ContainsKey(key)) return new U[0];
            return dict[key];
        }
        // example 'TjRole[] roles = cacheByName.Units<TjRole>("My Role").ToArray();'
        /// <summary>
        /// Returns all units with the stored with 'key' value and of the type 'T' or inherited types of 'T'.
        /// <para>Example" 'TjRole[] roles = cacheByName.Units&lt;TjRole&gt;("My Role").ToArray();'</para>
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <returns></returns>
        public IEnumerable<T> GetUnits<T>(string key) where T : class, IjStorageUnit
        {
            if (!dict.ContainsKey(key)) return new T[0];
            return dict[key].Where(u => u is T).Cast<T>();
        }
        /// <summary>
        /// Returns the a unit with the key of specified type 'T' or a
        /// unit with a key the inherits it type from type 'T'.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <returns></returns>
        public T Get<T>(string key) where T : class, IjStorageUnit
        {
            return dict[key].Where(u => u is T).Cast<T>().Single();
        }
        /// <summary>
        /// Returns the a unit with the key of specified type 'T'.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <returns></returns>
        public T GetWithSpecificType<T>(string key) where T : class, IjStorageUnit
        {
            return dict[key].Where(u => u.GetType() == typeof(T)).Cast<T>().Single();
        }
        #endregion
        #region TryGetSingleUnit
        /// <summary>
        /// Returns false if no unit was found.
        /// Returns false if multiple units of the same type ('T') with the same key are found.
        /// Returns true if a unit was found with the
        /// name specified by the 'key'.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <param name="unit"></param>
        /// <returns></returns>
        public bool TryGetSingleUnit<T>(string key, out T unit) where T : class, IjStorageUnit
        {
            if(!dict.ContainsKey(key))
            {
                unit = null;  // maybe should be an empty unit
                return false;
            }
            IEnumerable<T> units = dict[key].Where(u => u is T).Cast<T>();
            if (units.Count() == 1)
            {
                unit = units.Single();
                return true;
            }
            unit = null;
            return false;
        }
        #endregion
    }
}
